﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using Arroguella.Panels;
using Arroguella.Tiles;
using System.Text;
using Arroguella.Entities;
using System.Collections.Generic;
using System.Diagnostics;
using Arroguella.Panels.Popups;

namespace Arroguella.Screens
{
    public class GameScreen : Screen
    {
        #region Constructor

        public GameScreen(ContentManager contentMgr, EventHandler theScreenEvent, GraphicsDevice graphicsDevice, Arroguella arroguella)
            : base(theScreenEvent, graphicsDevice, arroguella)
        {
            // Dungeon seed management
            DungeonLevel = 0;
            MapSeeds = new List<Guid>();
            Guid seed = Guid.NewGuid();
            MapSeeds.Add(seed);

            // Set up the PlayerInfo panel
            PlayerInfoPanel = new PlayerInfoPanel(arroguella);
            PlayerInfoPanel.BackgroundColor = Color.Black;
            const int playerInfoPanelWidth = 200;
            PlayerInfoPanel.SetSizeAndShape(TitleSafeArea.Left, TitleSafeArea.Top, playerInfoPanelWidth, TitleSafeArea.Height);

            // Set up the message panel
            MessagePanel = new MessagePanel(arroguella);
            MessagePanel.Initialize();
            const int msgPanelHeight = 180;
            MessagePanel.SetSizeAndShape(TitleSafeArea.Left + playerInfoPanelWidth, TitleSafeArea.Bottom - msgPanelHeight, (TitleSafeArea.Width - playerInfoPanelWidth)/2, msgPanelHeight);
            MessagePanel.Message("Welcome to Arroguella! This is just a development preview.", Color.SeaGreen);
            MessagePanel.Message("Use the W, A, S, D keys to move. Use \'<\' to go downstairs; use \'>\' to go upstairs.", Color.DarkGray);

            // Set up the combat panel
            CombatPanel = new MessagePanel(arroguella);
            CombatPanel.Initialize();
            CombatPanel.SetSizeAndShape(MessagePanel.SizeAndShape.Right, TitleSafeArea.Bottom - msgPanelHeight, MessagePanel.SizeAndShape.Width, msgPanelHeight);
                      
            // Set up the GameMap panel
            MakeGameMap(arroguella);                   

            // Set up the Look panel
            LookPanel = new LookPanel(arroguella);
            LookPanel.Initialize();
            const int lookPanelHeight = 20;
            LookPanel.SetSizeAndShape(TitleSafeArea.Left + playerInfoPanelWidth, MessagePanel.SizeAndShape.Y - lookPanelHeight, TitleSafeArea.Width - playerInfoPanelWidth, lookPanelHeight);
        }

        #endregion

        #region Events

        /// <summary>
        /// Actions that occur when the Player's LevelUp event is triggered.
        /// </summary>
        public void Player_LeveledUp(object sender, Player.LevelUpEventArgs e)
        {
            MessagePanel.Message(string.Format("LEVEL UP! You are now level {0}!", e.Level), Color.Purple);
        }

        /// <summary>
        /// Actions that occur when the Player dies.
        /// </summary>
        public void Player_Death(object sender, Player.DeathEventArgs e)
        {
            MessagePanel.Message("You have died. Hit ESC to return to the main menu.", Color.Red);
            MessagePanel.IsLocked = true; // Prevent ANY further messages.
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a new Inventory Menu and shows it
        /// </summary>
        private void CreateAndShowInventoryMenu()
        {
            Rectangle mapRect = GameMap.SizeAndShape;
            Rectangle rect = new Rectangle(mapRect.X + 60, mapRect.Y + 40, mapRect.Width - 120, mapRect.Height - 80);
            Popup = InventoryMenu.CreateInventoryMenu(Arroguella, rect, Player.Inventory);
        }

        /// <summary>
        /// Creates a new Character Sheet and shows it
        /// </summary>
        private void CreateAndShowCharacterSheet()
        {
            Rectangle mapRect = GameMap.SizeAndShape;
            Rectangle rect = new Rectangle(mapRect.X + 60, mapRect.Y + 40, mapRect.Width - 120, mapRect.Height - 80);
            Popup = new CharacterSheet(Player, rect, Arroguella);
        }

        /// <summary>
        /// Generates a new game map, based on the current dungeon level
        /// </summary>
        /// <param name="arroguella"></param>
        private void MakeGameMap(Arroguella arroguella)
        {
            // New dungeon level? Let's add a new seed to the list of MapSeeds
            if (MapSeeds.Count < DungeonLevel+1)
            {
                MapSeeds.Add(Guid.NewGuid());
            }

            Debug.Assert(DungeonLevel+1 <= MapSeeds.Count, "Missing map seed!");

            // Save reference to current player
            Player player = null;
            if (GameMap != null && Player != null)
            {
                player = Player;
            }

            Guid seed = MapSeeds[DungeonLevel];
            MessagePanel.SystemMessage("Dungeon Level " + DungeonLevel + " seed " + seed.ToString());
            mousePosition = new Vector2(0, 0);
            GameMap = new GameMap(arroguella, seed);
            int gameMapHeight = TitleSafeArea.Height - MessagePanel.SizeAndShape.Height;
            GameMap.MakeNewMap(TitleSafeArea.Left + PlayerInfoPanel.SizeAndShape.Width, TitleSafeArea.Top,
                TitleSafeArea.Width - PlayerInfoPanel.SizeAndShape.Width, gameMapHeight, DungeonLevel, player);

            // Hook up event listeners for the Player's events
            Debug.Assert(Player != null);
            Player.LevelUp += Player_LeveledUp;
            Player.Death += Player_Death;
        }

        /// <summary>
        /// Update all of the elements that need updating in the Game Screen  
        /// </summary>
        /// <param name="theTime"></param>      
        public override void Update(GameTime theTime)
        {
            if (DateTime.Now - Arroguella.LastKeyPressTime < Arroguella.KEY_PRESS_LIMIT)
            {
                return;
            }

            KeyboardState keyboardState = Keyboard.GetState(PlayerOne);
            MouseState mouseState = Mouse.GetState();

            Arroguella.LastKeyPressTime = DateTime.Now;
           
            // Go back to the title screen
            if (keyboardState.IsKeyDown(Keys.Escape))
            {
                keyboardState = new KeyboardState();
                ScreenEvent.Invoke(this, new EventArgs());
            }

            // Do nothing if the player is dead.
            if (Player != null && Player.CurrentHealth <= 0)
            {
                return;
            }

            // Bring up Inventory menu
            if (keyboardState.IsKeyDown(Keys.I))
            {
                if (Popup == null)
                {
                    CreateAndShowInventoryMenu();
                }
                else
                {
                    Popup = null;
                }

                // Pause automatically
                keyboardState = new KeyboardState();
                isPaused = !isPaused;
            }

            // Bring up Character Sheet
            if (keyboardState.IsKeyDown(Keys.C))
            {
                if (Popup == null)
                {
                    CreateAndShowCharacterSheet();
                }
                else
                {
                    Popup = null;
                }

                // Pause automatically
                keyboardState = new KeyboardState();
                isPaused = !isPaused;
            }

            // Space bar toggles PAUSE on and off
            if (keyboardState.IsKeyDown(Keys.Space))
            {
                keyboardState = new KeyboardState();
                IsPaused = !IsPaused;
            }

            // Handle all other input
            if (!IsPaused)
            {
                HandleMouseInput(mouseState);
                HandlePlayerMovement(keyboardState);
                HandlePlayerActions(keyboardState);
            }
            // If we are paused and there is a popup menu, forward input to it.
            else if (Popup != null)
            {
                if (Popup is InventoryMenu)
                { 
                    HandleInventoryMenuInput(keyboardState); 
                }
            }
            

            //
            // Update Game Map and ALL OTHER GAME LOGIC!
            //
            if(!IsPaused)
            {
                GameMap.Update(theTime);
            }

            PlayerInfoPanel.Update(theTime);

            base.Update(theTime);            
        }

        /// <summary>
        /// Check the keyboard state for and handle input while the inventory popup menu is active
        /// </summary>
        /// <param name="keyboardState"></param>
        private void HandleInventoryMenuInput(KeyboardState keyboardState)
        {
            InventoryMenu popup = Popup as InventoryMenu;
            // Select Previous
            if (keyboardState.IsKeyDown(Keys.W) || keyboardState.IsKeyDown(Keys.A) || keyboardState.IsKeyDown(Keys.Up))
            {
                popup.SelectPrevious();
            }

            // Select Next
            if (keyboardState.IsKeyDown(Keys.S) || keyboardState.IsKeyDown(Keys.D) || keyboardState.IsKeyDown(Keys.Down))
            {
                popup.SelectNext();
            }

            // Select
            if (keyboardState.IsKeyDown(Keys.Enter))
            {
                object selected = popup.Select();

                if (selected == null)
                {
                    return;
                }
            }

            // Equip
            if (keyboardState.IsKeyDown(Keys.E))
            {
                EquipmentEntity entity = (Popup as InventoryMenu).Equip();

                if (entity != null)
                {
                    // Un-equip
                    if (entity.IsEquipped)
                    {
                        MessagePanel.Message(string.Format("You unequip {0} from {1}.", entity.Name, entity.EquippedSlot), entity.Color);

                        Player.Unequip(entity);                        
                    }
                    else
                    {
                        Player.Equip(entity);

                        if (entity.IsEquipped)
                        {
                            MessagePanel.Message(string.Format("You equip {0} to {1}.", entity.Name, entity.EquippedSlot), entity.Color);
                        }
                        else
                        {
                            MessagePanel.Message(string.Format("No slots to equip {0}!", entity.Name), Color.Pink);
                        }                                               
                    }
                }
            }

            // Drop
            if (keyboardState.IsKeyDown(Keys.G))
            {
                object selected = popup.Select();

                if (selected == null)
                {
                    return;
                }

                popup.RemoveSelectedChoice();

                Entity droppedEntity = Player.Drop(selected.ToString());
                Drop(droppedEntity);
            }

            // Examine
            if (keyboardState.IsKeyDown(Keys.X))
            {
                object selected = popup.Select();

                if (selected == null)
                {
                    return;
                }

                MessagePanel.Message(string.Format("This appears to be {0}, and that is the extent of your knowledge on the subject.", selected.ToString()), Color.LightSeaGreen);
            }

            // Cancel
            if (keyboardState.IsKeyDown(Keys.Back))
            {
                // Unpause automatically
                keyboardState = new KeyboardState();
                isPaused = !isPaused;

                Popup = null;
            }

            keyboardState = new KeyboardState();
        }

        /// <summary>
        /// Check the keyboard state for and handle player movement.
        /// </summary>
        private void HandlePlayerMovement(KeyboardState keyboardState)
        {
            if (keyboardState.IsKeyDown(Keys.D) || keyboardState.IsKeyDown(Keys.Right))
            {
                GameMap.MovePlayerRight(1);
            }

            if (keyboardState.IsKeyDown(Keys.A) || keyboardState.IsKeyDown(Keys.Left))
            {
                GameMap.MovePlayerLeft(1);
            }

            if (keyboardState.IsKeyDown(Keys.W) || keyboardState.IsKeyDown(Keys.Up))
            {
                GameMap.MovePlayerUp(1);
            }

            if (keyboardState.IsKeyDown(Keys.S) || keyboardState.IsKeyDown(Keys.Down))
            {
                GameMap.MovePlayerDown(1);
            }  
        }

        /// <summary>
        /// Check the keyboard state for and handle player actions.
        /// </summary>
        /// <param name="keyboardState"></param>
        private void HandlePlayerActions(KeyboardState keyboardState)
        {
            Tile playerTile = GameMap.GetPlayerTile();

            if (playerTile == null)
            {
                return;
            }

            bool isShiftKeyDown = keyboardState.IsKeyDown(Keys.LeftShift)
                ||keyboardState.IsKeyDown(Keys.RightShift);

            // GET ALL
            if (keyboardState.IsKeyDown(Keys.G) && isShiftKeyDown)
            {
                GetAll(playerTile);
            }
            // GET
            else if (keyboardState.IsKeyDown(Keys.G))
            {
                Get(playerTile);
            }

            #region Stairs

            if (isShiftKeyDown)
            {
                // Go Up
                if (keyboardState.IsKeyDown(Keys.OemPeriod) && playerTile.HasEnvironmentalEntity)
                {
                    GoUpStairs(playerTile);
                }

                // Go Down
                if (keyboardState.IsKeyDown(Keys.OemComma) && playerTile.HasEnvironmentalEntity)
                {
                    GoDownStairs(playerTile);
                }
            }

            #endregion   
        }

        /// <summary>
        /// Check the mouse state for movement and handle it
        /// </summary>
        /// <param name="mouseState"></param>
        private void HandleMouseInput(MouseState mouseState)
        {
            foreach (Tile tile in GameMap.TileEngine.TileMap.Values)
            {
                tile.IsHighlight = false;
            }

            Tile t = GameMap.GetTileAtPixelCoordinates(mouseState.X - PlayerInfoPanel.SizeAndShape.Width, mouseState.Y);

            StringBuilder sb = new StringBuilder();

            if (t != null)
            {
                t.IsHighlight = true;

                if (t.IsPlayerOccupied)
                {
                    sb.Append("yourself");
                }

                if (t.Objects.Count > 0)
                {
                    if (t.IsPlayerOccupied)
                    {
                        sb.Append(", and ");
                    }

                    string comma = string.Empty;
                    foreach (Entity obj in t.Objects)
                    {
                        if (obj.IsVisible)
                        {
                            sb.AppendFormat("{0}{1}", comma, obj.Name);
                            comma = @", ";
                        }
                    }
                }
                else if(t.Objects.Count <= 0 && !t.IsPlayerOccupied)
                {
                    sb.Append("nothing");
                }

                // TODO - Uncomment this for helpful debugging
                //sb.Append(string.Format(" at Tile [{0}, {1}]", t.Coords.X, t.Coords.Y));
            }
            else
            {
                sb.Append("nothing");
            }            

            LookPanel.Text = sb.ToString();
        }

        #region Player Actions

        /// <summary>
        /// Gets the first applicable entity from the tile on which the player is standing.
        /// </summary>
        private void Get(Tile playerTile)
        {
            if (playerTile.Objects.Count > 0)
            {
                List<Entity> entitiesToRemove = new List<Entity>();
                foreach (Entity entity in playerTile.Objects)
                {
                    // Can't pick up environmental entities
                    if (entity.IsEnvironmental)
                    {
                        continue;
                    }

                    // Money entities get added to player money, not inventory
                    if (entity is MoneyEntity)
                    {
                        float value = (entity as MoneyEntity).Value;
                        Player.Money += value;
                        entitiesToRemove.Add(entity);
                        MessagePanel.Message(string.Format("You picked up {0} worth {1} GP.", entity.Name.ToUpperInvariant(), value), Color.Gold);
                        break;
                    }

                    // Picking up dead things
                    /*
                    if (entity is CreatureEntity && (entity as CreatureEntity).CurrentHealth <= 0)
                    {
                        MessagePanel.Message("Um, picking up remains? Gross much? Let's not do that.", Color.SeaGreen);
                        return;
                    }
                     */

                    // Pick up the item and put it in inventory
                    if (Player.Get(entity))
                    {
                        entitiesToRemove.Add(entity);
                        MessagePanel.Message(string.Format("You added {0} to your inventory.", entity.Name.ToUpperInvariant()), entity.Color);
                        break;
                    }
                    else
                    {
                        MessagePanel.Message(string.Format("You tried to pick up {0}, but you can't carry it!", entity.Name.ToUpperInvariant()), Color.Green);
                    }
                }

                // Couldn't modify the list while we were iterating over it.
                foreach (Entity pickedUpEntity in entitiesToRemove)
                {
                    playerTile.Objects.Remove(pickedUpEntity);
                }
            }
            else
            {
                MessagePanel.Message("There is nothing to get here!", Color.Green);
            }
        }

        /// <summary>
        /// Gets all applicable entities from the tile on which the player is standing.
        /// </summary>
        private void GetAll(Tile playerTile)
        {
            if (playerTile.Objects.Count > 0)
            {
                List<Entity> entitiesToRemove = new List<Entity>();
                foreach (Entity entity in playerTile.Objects)
                {
                    // Can't pick up environmental entities
                    if (entity.IsEnvironmental)
                    {
                        continue;
                    }

                    // Money entities get added to player money, not inventory
                    if (entity is MoneyEntity)
                    {
                        float value = (entity as MoneyEntity).Value;
                        Player.Money += value;
                        entitiesToRemove.Add(entity);
                        MessagePanel.Message(string.Format("You picked up {0} worth {1} GP.", entity.Name.ToUpperInvariant(), value), Color.Gold);
                        continue;
                    }

                    // Picking up dead things
                    if (entity is CreatureEntity && (entity as CreatureEntity).CurrentHealth <= 0)
                    {
                        MessagePanel.Message("Um, picking up remains? Gross much? Let's not do that.", Color.SeaGreen);
                        return;
                    }

                    // Pick up the item and put it in inventory
                    if (Player.Get(entity))
                    {
                        entitiesToRemove.Add(entity);
                        MessagePanel.Message(string.Format("You added {0} to your inventory.", entity.Name.ToUpperInvariant()), entity.Color);
                    }
                    else
                    {
                        MessagePanel.Message(string.Format("You tried to pick up {0}, but you can't carry it!", entity.Name.ToUpperInvariant()), Color.Green);
                    }
                }

                // Couldn't modify the list while we were iterating over it.
                foreach (Entity pickedUpEntity in entitiesToRemove)
                {
                    playerTile.Objects.Remove(pickedUpEntity);
                }
            }
            else
            {
                MessagePanel.Message("There is nothing to get here!", Color.Green);
            }
        }

        // TODO - Implement this
        private void Drop(Entity entity)
        {
            Tile playerTile = GameMap.TileEngine.TileMap[Player.Coords];
            if (playerTile != null && entity != null)
            {
                playerTile.Objects.Add(entity);
            }
        }

        /// <summary>
        /// Goes down one level.
        /// </summary>
        private void GoDownStairs(Tile playerTile)
        {
            foreach (Entity obj in playerTile.Objects)
            {
                if (obj is DownStairs)
                {
                    MessagePanel.Message("You descend deeper into the dungeon...", Color.Blue);
                    DungeonLevel++;
                    MakeGameMap(Arroguella);
                    break;
                }
            }
        }

        /// <summary>
        /// Goes up one level.
        /// </summary>
        private void GoUpStairs(Tile playerTile)
        {
            // Can't go up if we are already on level 0
            if (DungeonLevel == 0)
            {
                return;
            }

            foreach (Entity obj in playerTile.Objects)
            {
                if (obj is UpStairs)
                {
                    MessagePanel.Message("You ascend from this dungeon level...", Color.Blue);
                    DungeonLevel--;
                    MakeGameMap(Arroguella);
                    break;
                }
            }
        }

        #endregion

        //Draw all of the elements that make up the Title Screen
        public override void Draw(SpriteBatch spriteBatch)
        {
            // Background
            DrawBackground(spriteBatch);            

            // Draw the GameMap Panel
            GameMap.Draw(spriteBatch);

            // Draw the PlayerInfo Panel
            PlayerInfoPanel.Draw(spriteBatch);

            // Draw the Message Panel
            MessagePanel.Draw(spriteBatch);   
 
            // Draw the Combat Panel
            CombatPanel.Draw(spriteBatch);
            
            // Draw the Look Panel
            LookPanel.Draw(spriteBatch);

            // If there is a popup to display, draw it
            if (Popup != null)
            {
                Popup.Draw(spriteBatch);
            }

            base.Draw(spriteBatch);
        }

        #endregion

        #region Debug Methods

        private void DebugRegenerateDungeon()
        {
            Guid seed = Guid.NewGuid();
            MapSeeds[DungeonLevel] = seed;
            MakeGameMap(Arroguella);
        }

        #endregion

        #region Properties

        #region Panels

        internal MessagePanel MessagePanel;

        internal MessagePanel CombatPanel;

        internal LookPanel LookPanel;

        internal PlayerInfoPanel PlayerInfoPanel;

        public GameMap GameMap;

        #endregion        

        #region Popups

        internal ScreenPanel Popup;

        #endregion

        private Vector2 mousePosition;

        public List<Guid> MapSeeds;

        public int DungeonLevel { get; set; }

        public Player Player { get { return GameMap.Player; } }

        /// <summary>
        /// Whether or not the game is paused.
        /// If the game is paused, we do not want AI or game logic to continue updating,
        /// but we might want other things to happen, such as keyboard or mouse input.
        /// </summary>        
        public bool IsPaused
        {
            get { return isPaused; }
            set
            {
                isPaused = value;
                if(Popup == null)
                {
                    MessagePanel.SystemMessage(string.Format("[{1}] Game {0}.", isPaused ? "PAUSED" : "Unpaused", DateTime.Now)); 
                }
            }
        }
        private bool isPaused;

        #endregion
    }
}
